import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Random;

/***
 * This class holds all the stuff for neighborhood structures and a neighbor/solution.
 *
 */
public class Solution {
	/***
	 * Our datastructure is a two-dimensional Integer array: the column i 
	 * represents the schedule of team i and the row j represents the round j. 
	 * A value of the array is the number of the opposing team, a negative 
	 * integer means the game is away, a positive that it's at home. An example: 
	 * 
	 *  	0	1	2	3
	 *  0	+1	-0	+3	-2
	 *  1	+2	-3	+1	-0
	 *  2	-3	.
	 *  3	-1		.
	 *  4	-2			.
	 *  5	+3
	 */
	
	public static int[][] distanceLookupTable;
	public static int n;
	private int[][] schedule;
	private int violatedConstraints = -1;
	private int costs = -1;
	
	public Solution(int n) {
		// create an initial solution for a number of n teams
    	int initialSchedule[][] = new int[n][2*(n-1)];
    	
    	//Random generator
        Random generator = new Random();
        
        //random plays of the first round will appear in randomArray 
        int firstRound[] = new int[n];
        ArrayList<Integer> intNumbers =  new ArrayList<Integer>();
        for(int k=0; k<n; k++){
        	intNumbers.add(k);
        }
        for(int i=0; i < n; i++){
        	int randomIndex = generator.nextInt(n-i);
        	firstRound[i] = intNumbers.get(randomIndex); 
        	intNumbers.remove(randomIndex);
        }
        
        int roundSchedule[] = new int[n];
        System.arraycopy(firstRound, 0, roundSchedule, 0, n);
        
        //writing first round
        for(int j=0; j<n; j++)
        {
        	if(j<n/2) //home and away
        	{
        		initialSchedule[firstRound[j]][0] = firstRound[n-j-1]+1;//+1: Team names starts from 1 to n
        	}
        	else
        	{
        		initialSchedule[firstRound[j]][0] = -(firstRound[n-j-1]+1); //+1: Team names starts from 1 to n //for away teams
        	}
        }
        
       
        
        //writing all other rounds of first round robin
        for(int l=1; l<(n-1);l++)
        {
        	int temp1 = roundSchedule[n-1];
        	int temp2 = roundSchedule[1];
        	//fix one competitor (index number one) and rotate the others clockwise
        	for(int r = 1; r<n; r++)
        	{
        		if(r<n-1)
        		{
        			temp1 = roundSchedule[r+1];
        			roundSchedule[r+1] = temp2;
        			temp2=temp1;
        		}
        		else
        		{
        			 roundSchedule[1] = temp2;
        			
        		}
        	}

        	for(int m=0; m<n; m++)
            {
        		
        		int homeAwayFair;
        		if (l%2 == 0)
        			{
        			homeAwayFair= -1;
        			}
        		else {
        			homeAwayFair= 1;
        		}
            	if(m<n/2) //home and away
            	{
            	
            		initialSchedule[roundSchedule[m]][l] = homeAwayFair*(roundSchedule[n-m-1]+1);//+1: Team names starts from 1 to n
            	}
            	else
            	{
            		initialSchedule[roundSchedule[m]][l] = -homeAwayFair*(roundSchedule[n-m-1]+1); //+1: Team names starts from 1 to n //for away teams
            	}
            }
        }
        
        //writing all rounds of second round robin
        for(int p=0; p<n-1;p++)
        {
        	for(int q=0; q<n; q++)
        	{
        		initialSchedule[roundSchedule[q]][p+n-1] = -initialSchedule[roundSchedule[q]][p];
        	}
        }
        this.schedule = initialSchedule;
		swapRounds(n,2*(n-1)-1);
	}
	
	public Solution(int[][] schedule) {
		this.schedule = new int[schedule.length][schedule[0].length];
		for (int i = 0; i < schedule.length; i++) {
			for (int j = 0; j < schedule[0].length; j++) {
				this.schedule[i][j] = schedule[i][j];
			}
		}
	}
	public Solution copy() {
		Solution copy = new Solution(this.schedule);
		return copy;
	}
	
	public int evaluate1() {
		//first hard Constraints
		int counter = 0;
		int result = -1;
		if (violatedConstraints == -1) {
			//TODO: compute the number of violated constraints
			
			for(int i=0; i<n; i++)
			{
				for (int j=0; j<2*(n-1)-1;j++)
				{
					if(Math.abs(schedule[i][j])==Math.abs(schedule[i][j+1])){
						counter++;
					}
				}
			}
			result = counter/2;
			
		} else {
			result = violatedConstraints;
		}
		return result;
	}
	
	public int evaluate2() {
		//second hard Constraints
		
		int result = -1;
		if (violatedConstraints == -1) {
			//compute the number of violated constraints
			int homeCounter = 0 ;
			int awayCounter = 0;
			int counter = 0;
			
			for(int i=0; i<n; i++)
			{
				homeCounter = 0;
				awayCounter = 0;
				
				for (int j=0; j<2*(n-1);j++)
				{
					if (schedule[i][j]<0)
					{
						homeCounter = 0;
						awayCounter++;
						if(awayCounter>3)
						{
							counter++;
						}
					}
					else
					{
						awayCounter = 0;
						homeCounter++;
						if(homeCounter>3)
						{
							counter++;
						}
					}
				}	
			}
			result = counter;
			
		} else {
			result = violatedConstraints;
		}
		return result;
	}
	
	public int evaluate() {
		int result = -1;
		if (violatedConstraints == -1) {
			// compute the number of violated constraints
			result = evaluate1()+evaluate2();
		} else {
			result = violatedConstraints;
		}
		return result;
	}
	
	public int cost() {
		int result = -1;
		int cost = 0;
		if (costs == -1) {
			//return the cost of this solution
			

			for(int i=0; i<n; i++)
			{
				//costs of the first round for away teams
				if(schedule[i][0]<0)
				{
					cost+=distanceLookupTable[i][Math.abs(schedule[i][0])-1];
				}
				for (int j=1; j<2*(n-1);j++) //costs from second round to the end
				{
					if(schedule[i][j]<0 || schedule[i][j-1]<0) //If the team does not remain at home for two weeks
					{
						if(schedule[i][j]>0 && schedule[i][j-1]<0) //Previous round away / This round home
						{
							cost+=distanceLookupTable[Math.abs(schedule[i][j-1])-1][i];
						}
						else if(schedule[i][j]<0 && schedule[i][j-1]>0) // Previous round home / This round away
						{
							cost+=distanceLookupTable[i][Math.abs(schedule[i][j])-1];
						}
						else //The team was away in both games
						{
							cost+=distanceLookupTable[Math.abs(schedule[i][j])-1][Math.abs(schedule[i][j-1])-1];
						}
					}
				}
				//costs of the first round for away teams
				if(schedule[i][2*(n-1)-1]<0)
				{
					cost+=distanceLookupTable[i][Math.abs(schedule[i][2*(n-1)-1])-1];
				}
			}
			result = cost;
			
		} else {
			result = costs;
		}
		return result;
	}
	
	public void swapHome(int t1) {
		// swap the home/away pattern of the given team
//		for(int i=0; i<n; i++){
			for(int j=0; j<2*(n-1);j++)
			{
				schedule[t1][j]= -schedule[t1][j]; //swap home/away of t1
				schedule[Math.abs(schedule[t1][j])-1][j] = - schedule[Math.abs(schedule[t1][j])-1][j]; //swap home/away of other teams, which should play with t1
			}
//		}
	}
	
	public void swapTeams(int t1, int t2) {
		// swap the teams t1 and t2
		int[] temp =  schedule[t1];
		schedule[t1] = schedule[t2];
		schedule[t2] = temp;
		for(int i=0; i<n; i++){
			for(int j=0; j<2*(n-1);j++)
			{
				if(schedule[i][j]==(t1+1))
				{
						schedule[i][j]=(t2+1);
				}
				else if(schedule[i][j]==-(t1+1))
				{
						schedule[i][j]=(-t2-1);
				}
				else if(schedule[i][j]==(t2+1))
				{
						schedule[i][j]=(t1+1);
				}
				else if(schedule[i][j]==-(t2+1))
				{
						schedule[i][j]=(-t1-1);
				}
			}
		}
	}
	
	public void swapRounds(int r1, int r2) {
		// swap two rounds
		
		int[] temp = new int[n];
		for(int i=0; i < n; i++)
		{
			temp[i] = schedule[i][r1];
			schedule[i][r1] = schedule[i][r2];
		}
		for(int j=0; j < n; j++)
		{
			schedule[j][r2] = temp[j];
		}
	}
	
	/***
	 * swap the part behind the cut with the part of the schedule before the cut
	 * @param cut where to "cut" the schedule apart
	 * @return the swapped schedule
	 */
	public void swapHalves(int cut) {
		int[][] newSched = new int[schedule.length][schedule[0].length];
		for (int i = 0; i < schedule.length; i++) {
			int k = 0;
			for (int j = cut; j < schedule[0].length; j++) {
				newSched[i][j] = schedule[i][k];
				k++;
			}
			for (int j = 0; j < cut; j++) {
				newSched[i][j] = schedule[i][k];
				k++;
			}
		}
		schedule = newSched;
	}
	
	public void plusOne() {
		for (int i = 0; i < schedule.length; i++) {
			for (int j = 0; j < schedule[0].length; j++) {
					if(schedule[i][j] < 0) {
						schedule[i][j]--;
						if (schedule[i][j] < n*(-1)) {
							schedule[i][j] = -1;
						}
						if (schedule[i][j] == (i+1)*(-1)) {
							schedule[i][j]--;
						}
						if (schedule[i][j] < n*(-1)) {
							schedule[i][j] = -1;
						}
					} else {
						schedule[i][j]++;
						if (schedule[i][j] > n) {
							schedule[i][j] = 1;
						}
						if (schedule[i][j] == (i+1)) {
							schedule[i][j]++;
						}
						if (schedule[i][j] > n) {
							schedule[i][j] = 1;
						}
					}
			}
		}
	}
	
	public void output(boolean commandline, boolean file, boolean extraInformation){
		if(commandline)
			{
				for(int i=0; i<n; i++)
				{
					String outString = "";
					for(int j=0; j<2*(n-1); j++)
					{
						outString += schedule[i][j]+" ";
					}
					System.out.println(outString);
				}
				if(extraInformation)
				{
					System.out.println("Input Path: "+Main.path);
					System.out.println("Output Format: Anagnostopoulosh");
					System.out.println("Total Distance = "+cost());
					System.out.println("AtMost Violations = "+evaluate2());
					System.out.println("NoRepeat Violations = "+evaluate1());
					System.out.println("Total Violations = "+evaluate());
				}
				
			}
		if(file)
		{
			for(int i=0; i<n; i++)
			{
				String outString = "";
				for(int j=0; j<2*(n-1); j++)
				{
					outString += schedule[i][j]+" ";
				}
				writeLinesToFile(Main.path.replace(".txt", "_Schedule.txt"),outString.trim(),true);
				
			}
			if(extraInformation)
			{
				writeLinesToFile(Main.path.replace(".txt", "_Schedule.txt"),"Input Path: "+Main.path,true);
				writeLinesToFile(Main.path.replace(".txt", "_Schedule.txt"),"Output Format: Anagnostopoulosh",true);
				writeLinesToFile(Main.path.replace(".txt", "_Schedule.txt"),"Total Distance = "+cost(),true);
				writeLinesToFile(Main.path.replace(".txt", "_Schedule.txt"),"AtMost Violations = "+evaluate2(),true);
				writeLinesToFile(Main.path.replace(".txt", "_Schedule.txt"),"NoRepeat Violations = "+evaluate1(),true);
				writeLinesToFile(Main.path.replace(".txt", "_Schedule.txt"),"Total Violations = "+evaluate(),true);
			}
		}
	}
	
	private boolean writeLinesToFile(String filename,String linesToWrite,
			boolean appendToFile) {
		
	  		PrintWriter pw = null;
	  		try {
	  			if (appendToFile) {
	  				//If the file already exists, start writing at the end of it.
	  				pw = new PrintWriter(new FileWriter(filename, true));
	  			}
	  			else {
	  				pw = new PrintWriter(new FileWriter(filename));
	  			}
	  			pw.println(linesToWrite);
	  			
	  			pw.flush();
	  		}
	  		catch (IOException e) {
	  			e.printStackTrace();
	  			return false;
	  		}
	  		finally {

	  			//Close the PrintWriter
	  			if (pw != null)
	  				pw.close();
	  		}
	  		return true;
	}
}
